﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace FoundationLib.Common.Extensions
{
	/// <summary>
	/// Defines extension methods for the <see cref="DateTime"/> type.
	/// </summary>
	public static class DateTimeExtensions
	{		
		/// <summary>
		/// Returns the difference (in days, hours, or minutes) between the current time and the given value.
		/// </summary>
		/// <param name="value">The <see cref="DateTime"/> value</param>
		/// <param name="dateTimeFormat">The date/time format for differences greater than <paramref name="maxDays"/> 
		/// and <paramref name="maxHours"/></param>
		/// <param name="maxDays">The maximum number of days</param>
		/// <param name="maxHours">The maximum number of hours</param>
		/// <returns>The string representation of the difference</returns>
		public static string DifferenceFromNow(this DateTime value, string dateTimeFormat, double maxDays, double maxHours)
		{
			DateTime now = DateTime.Now;

			string prefix = null;
			string suffix = null;
			string precision = null;
			string format = null;

			int diff = 0;
			
			//(1) Determine whether value is in the future, past, or now
			if (now < value)
			{
				//Future
				prefix = "In ";
				suffix = "";
			}
			else if (now > value)
			{
				//Past
				prefix = "";
				suffix = " ago";
			}
			else
			{
				//Now
				prefix = "";
				suffix = "";
				format = "Now";
			}


			//(2) Determine precision (days or hours), but only if value is not now
			if (String.IsNullOrEmpty(format))
			{
				TimeSpan span = value > now ? (value - now) : (now - value);

				if (span >= TimeSpan.FromDays(1.0) && span <= TimeSpan.FromDays(maxDays))
				{
					//Days
					diff = (int)Math.Round(span.TotalDays, 0, MidpointRounding.AwayFromZero);
					precision = diff == 1 ? "day" : "days";
				}
				else if (span <= TimeSpan.FromHours(maxHours))
				{
					//Minutes or hours
					if (span < TimeSpan.FromHours(1.0))
					{
						diff = (int)Math.Round(span.TotalMinutes, 0, MidpointRounding.ToEven);
						precision = diff == 1 ? "minute" : "minutes";
					}
					else
					{
						diff = (int)Math.Round(span.TotalHours, 0, MidpointRounding.AwayFromZero);
						precision = diff == 1 ? "hour" : "hours";
					}
				}
				else if (span > TimeSpan.FromHours(maxHours) && span < TimeSpan.FromDays(1.0))
				{
					//Less than a day if greater than maxHours but less than 24 hours
					prefix += prefix == "" ? "Less than " : "less than ";
					diff = 1;
					precision = "day";

				}
				else
				{
					//Difference greater than maxDays -> return value in the requested format
					return (value.ToString(dateTimeFormat));
				}

				format = "{0} " + precision;
			}

			//(3) Build string
			StringBuilder b = new StringBuilder();
			b.Append(prefix);
			b.AppendFormat(format, diff);
			b.Append(suffix);

			return (b.ToString());
		}

		/// <summary>
		/// Returns the difference (in days, hours, or minutes) between the current time and the given value.
		/// </summary>
		/// <param name="value">The <see cref="DateTime"/> value</param>
		/// <param name="dateTimeFormat">The date/time format for differences greater than 
		/// <paramref name="maxDays"/> and 24 hours</param>
		/// <param name="maxDays">The maximum number of days</param>
		/// <returns>The string representation of the difference</returns>
		public static string DifferenceFromNow(this DateTime value, string dateTimeFormat, double maxDays)
		{
			return (DifferenceFromNow(value, dateTimeFormat, maxDays, 24.0));
		}

		/// <summary>
		/// Returns the difference (in days, hours, or minutes) between the current time and the given value.
		/// </summary>
		/// <param name="value">The <see cref="DateTime"/> value</param>
		/// <param name="dateTimeFormat">The date/time format for differences greater than 7 days and 24 hours</param>
		/// <returns>The string representation of the difference</returns>
		public static string DifferenceFromNow(this DateTime value, string dateTimeFormat)
		{
			return DifferenceFromNow(value, dateTimeFormat, 7.0, 24.0);
		}

		/// <summary>
		/// Tries to combine the given date and time strings and create a new <see cref="DateTime"/> instance.
		/// </summary>
		/// <param name="dateString">The date string</param>
		/// <param name="timeString">The time string</param>
		/// <returns>The <see cref="DateTime"/> instance or null on error</returns>
		public static DateTime? TryCreate(string dateString, string timeString)
		{
			string combinedString = dateString + " " + timeString;
			DateTime parsedValue = DateTime.MinValue;
			if (!DateTime.TryParse(combinedString, out parsedValue))
				return null;
			else
				return (new DateTime?(parsedValue));
		}

		/// <summary>
		/// Returns the current week of year of the specified date.
		/// </summary>
		/// <param name="value">The date</param>
		/// <returns>The week of year</returns>
		public static int WeekOfYear(this DateTime value)
		{
			CultureInfo culture = CultureInfo.CurrentCulture;
			return (culture.Calendar.GetWeekOfYear(value, culture.DateTimeFormat.CalendarWeekRule, 
				culture.DateTimeFormat.FirstDayOfWeek));
		}

		/// <summary>
		/// Converts the current instance to a UNIX timestamp.
		/// </summary>
		/// <param name="value">The current <see cref="DateTime"/> instance</param>
		/// <returns>A UNIX timestamp representing the given <see cref="DateTime"/> instance</returns>
		public static int ToUnixTimestamp(this DateTime value)
		{
			DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
			TimeSpan diff = value - origin;
			return (int)Math.Floor(diff.TotalSeconds);
		}
	}
}
